function violins = violinplot(data, cats, varargin)
%Violinplots plots violin plots of some data and categories
%   VIOLINPLOT(DATA) plots a violin of a double vector DATA
%
%   VIOLINPLOT(DATAMATRIX) plots violins for each column in
%   DATAMATRIX.
%
%   VIOLINPLOT(DATAMATRIX, CATEGORYNAMES) plots violins for each
%   column in DATAMATRIX and labels them according to the names in the
%   cell-of-strings CATEGORYNAMES.
%
%   In the cases above DATA and DATAMATRIX can be a vector or a matrix,
%   respectively, either as is or wrapped in a cell.
%   To produce violins which have one distribution on one half and another
%   one on the other half, DATA and DATAMATRIX have to be cell arrays
%   with two elements, each containing a vector or a matrix. The number of
%   columns of the two data sets has to be the same.
%
%   VIOLINPLOT(DATA, CATEGORIES) where double vector DATA and vector
%   CATEGORIES are of equal length; plots violins for each category in
%   DATA.
%
%   VIOLINPLOT(TABLE), VIOLINPLOT(STRUCT), VIOLINPLOT(DATASET)
%   plots violins for each column in TABLE, each field in STRUCT, and
%   each variable in DATASET. The violins are labeled according to
%   the table/dataset variable name or the struct field name.
%
%   violins = VIOLINPLOT(...) returns an object array of
%   <a href="matlab:help('Violin')">Violin</a> objects.
%
%   VIOLINPLOT(..., 'PARAM1', val1, 'PARAM2', val2, ...)
%   specifies optional name/value pairs for all violins:
%     'Width'        Width of the violin in axis space.
%                    Defaults to 0.3
%     'Bandwidth'    Bandwidth of the kernel density estimate.
%                    Should be between 10% and 40% of the data range.
%     'ViolinColor'  Fill color of the violin area and data points. Accepts
%                    1x3 color vector or nx3 color vector where n = num
%                    groups. In case of two data sets being compared it can 
%                    be an array of up to two cells containing nx3
%                    matrices.
%                    Defaults to the next default color cycle.
%     'ViolinAlpha'  Transparency of the violin area and data points.
%                    Can be either a single scalar value or an array of
%                    up to two cells containing scalar values.
%                    Defaults to 0.3.
%     'MarkerSize'   Size of the data points, if shown.
%                    Defaults to 24
% 'MedianMarkerSize' Size of the median indicator, if shown.
%                    Defaults to 36
%     'EdgeColor'    Color of the violin area outline.
%                    Defaults to [0.5 0.5 0.5]
%     'BoxColor'     Color of the box, whiskers, and the outlines of
%                    the median point and the notch indicators.
%                    Defaults to [0.5 0.5 0.5]
%     'MedianColor'  Fill color of the median and notch indicators.
%                    Defaults to [1 1 1]
%     'ShowData'     Whether to show data points.
%                    Defaults to true
%     'ShowNotches'  Whether to show notch indicators.
%                    Defaults to false
%     'ShowMean'     Whether to show mean indicator
%                    Defaults to false
%     'ShowBox'      Whether to show the box.
%                    Defaults to true
%     'ShowMedian'   Whether to show the median indicator.
%                    Defaults to true
%     'ShowWhiskers' Whether to show the whiskers
%                    Defaults to true
%     'GroupOrder'   Cell of category names in order to be plotted.
%                    Defaults to alphabetical ordering
%     'Orientation'  Orientation of the violin plot. 
%                    Defaults to 'vertical'.
%     'Parent'       The parent axis of the violin plot.

% Copyright (c) 2016, Bastian Bechtold
% This code is released under the terms of the BSD 3-clause license

hascategories = exist('cats','var') && not(isempty(cats));

%parse the optional grouporder argument
%if it exists parse the categories order
% but also delete it from the arguments passed to Violin
grouporder = {};
idx=find(strcmp(varargin, 'GroupOrder'));
if ~isempty(idx) && numel(varargin)>idx
    if iscell(varargin{idx+1})
        grouporder = varargin{idx+1};
        varargin(idx:idx+1)=[];
    else
        error('Second argument of ''GroupOrder'' optional arg must be a cell of category names')
    end
end

% check and correct the structure of ViolinColor input
idx=find(strcmp(varargin, 'ViolinColor'));
if ~isempty(idx) && iscell(varargin{idx+1})
    if length(varargin{idx+1}(:))>2
        error('ViolinColor input can be at most a two element cell array');
    end
elseif ~isempty(idx) && isnumeric(varargin{idx+1})
    varargin{idx+1} = varargin(idx+1);
end

% check and correct the structure of ViolinAlpha input
idx=find(strcmp(varargin, 'ViolinAlpha'));
if ~isempty(idx) && iscell(varargin{idx+1})
    if length(varargin{idx+1}(:))>2
        error('ViolinAlpha input can be at most a two element cell array');
    end
elseif ~isempty(idx) && isnumeric(varargin{idx+1})
    varargin{idx+1} = varargin(idx+1);
end

% tabular data
if isa(data, 'dataset') || isstruct(data) || istable(data)
    if isa(data, 'dataset')
        colnames = data.Properties.VarNames;
    elseif istable(data)
        colnames = data.Properties.VariableNames;
    elseif isstruct(data)
        colnames = fieldnames(data);
    end
    catnames = {};
    if isempty(grouporder)
        for n=1:length(colnames)
            if isnumeric(data.(colnames{n}))
                catnames = [catnames colnames{n}]; %#ok<*AGROW>
            end
        end
        catnames = sort(catnames);
    else
        for n=1:length(grouporder)
            if isnumeric(data.(grouporder{n}))
                catnames = [catnames grouporder{n}];
            end
        end
    end
    
    for n=1:length(catnames)
        thisData = data.(catnames{n});
        violins(n) = Violin({thisData}, n, varargin{:});
    end
    if strcmp(violins(1).Orientation,'vertical')
        set(violins(1).Parent, 'XTick', 1:length(catnames), 'XTickLabels', catnames);
    else
        set(violins(1).Parent, 'YTick', 1:length(catnames), 'YTickLabels', catnames);
    end
    set(violins(1).Parent,'Box','on');
    return
elseif iscell(data) && length(data(:))==2 % cell input
    if not(size(data{1},2)==size(data{2},2))
        error('The two input data matrices have to have the same number of columns');
    end
elseif iscell(data) && length(data(:))>2 % cell input
    error('Up to two datasets can be compared');
elseif isnumeric(data) % numeric input   
    % 1D data, one category for each data point
    if hascategories && numel(data) == numel(cats)    
        if isempty(grouporder)
            cats = categorical(cats);
        else
            cats = categorical(cats, grouporder);
        end
        
        catnames = (unique(cats)); % this ignores categories without any data
        catnames_labels = {};
        for n = 1:length(catnames)
            thisCat = catnames(n);
            catnames_labels{n} = char(thisCat);
            thisData = data(cats == thisCat);
            violins(n) = Violin({thisData}, n, varargin{:});
        end
        if strcmp(violins(1).Orientation,'vertical')
            set(violins(1).Parent, 'XTick', 1:length(catnames), 'XTickLabels', catnames);
        else
            set(violins(1).Parent, 'YTick', 1:length(catnames), 'YTickLabels', catnames);
        end
        set(violins(1).Parent,'Box','on');
        return
    else
        data = {data};
    end
end

% 1D data, no categories
if not(hascategories) && isvector(data{1})
    violins = Violin(data, 1, varargin{:});
    if strcmp(violins(1).Orientation,'vertical')
        set(violins(1).Parent, 'XTick', 1);
    else
        set(violins(1).Parent, 'yTick', 1);
    end
% 2D data with or without categories
elseif ismatrix(data{1})
    for n=1:size(data{1}, 2)
        thisData = cellfun(@(x)x(:,n),data,'UniformOutput',false);
        violins(n) = Violin(thisData, n, varargin{:});
    end
    if strcmp(violins(1).Orientation,'vertical')
        set(violins(1).Parent, 'XTick', 1:size(data{1}, 2));
    else
        set(violins(1).Parent, 'YTick', 1:size(data{1}, 2));
    end
    if hascategories && length(cats) == size(data{1}, 2)
        if strcmp(violins(1).Orientation,'vertical')
            set(violins(1).Parent, 'XTickLabels', cats);
        else
            set(violins(1).Parent, 'YTickLabels', cats);
        end
    end
end

set(violins(1).Parent,'Box','on');

end
